﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Diagnostics;
using Nine.Serialization;

namespace UnitTest
{
    [TestClass]
    public class PerformanceTest
    {
        /// <summary>
        /// Modified from http://theburningmonk.com/2011/08/performance-test-binaryformatter-vs-protobuf-net/
        /// </summary>
        [TestMethod]
        public void NineDotBinarySerializerVsProtoBufDotNet()
        {
            // speed test protobuf-net
            DoSpeedTest("Protobuf-Net", SimpleObjects, SerializeWithProtobufNet, DeserializeWithProtobufNet<SimpleObject>);

            // speed test Nine.BinarySerializer
            DoSpeedTest("Nine.BinarySerializer", SimpleObjects, SerializeWithNineBinarySerializer, DeserializeWithNineBinarySerializer<SimpleObject>);
        }
        
        // test serialization and deserialization on 100k objects
        private const int ObjectsCount = 100000;

        // perform 5 runs of each test
        private const int TestRuns = 5;

        // the objects to perform the tests with
        private static readonly List<SimpleObject> SimpleObjects = Enumerable.Range(1, ObjectsCount).Select(GetSimpleObject).ToList();

        private static void DoSpeedTest<T>(
            string testGroupName, List<T> objects, Action<Stream, T> serializeFunc, Func<Stream, T> deserializeFunc)
        {
            var testSamples = TestRuns * objects.Count;
            var stopwatch = new Stopwatch();
            var buffer = new MemoryStream(testSamples * 128);

            Console.WriteLine(testGroupName);

            var startBytes = GC.GetTotalMemory(true);

            stopwatch.Start();

            for (int i = 0; i < TestRuns; i++)
            {
                for (int j = 0; j < objects.Count; j++)
                {
                    serializeFunc(buffer, objects[j]);
                }
            }

            stopwatch.Stop();

            var endBytes = GC.GetTotalMemory(true);

            Console.WriteLine("Test Group [{0}] serialized time is [{1}] milliseconds for [{2}] samples", testGroupName, stopwatch.ElapsedMilliseconds, testSamples);
            Console.WriteLine("Test Group [{0}] serialized byte array size is [{1}]", testGroupName, buffer.Length / testSamples);

            buffer.Seek(0, SeekOrigin.Begin);

            startBytes = GC.GetTotalMemory(true);
            stopwatch.Start();

            var clones = new T[TestRuns];

            for (int i = 0; i < TestRuns; i++)
            {
                for (int j = 0; j < objects.Count; j++)
                {
                    clones[j % TestRuns] = deserializeFunc(buffer);
                }
            }

            stopwatch.Stop();
            endBytes = GC.GetTotalMemory(true);

            Console.WriteLine("Test Group [{0}] average deserialized time is [{1}] milliseconds for [{2}] samples", testGroupName, stopwatch.ElapsedMilliseconds, testSamples);

            Console.WriteLine("--------------------------------------------------------");
        }

        private static SimpleObject GetSimpleObject(int id)
        {
            return new SimpleObject
            {
                Name = "Simple",
                Id = 100000,
                Address = "Planet Earth",
                Scores = Enumerable.Range(0, 10).ToList()
            };
        }

        private static void SerializeWithProtobufNet<T>(Stream output, T obj)
        {
            ProtoBuf.Serializer.Serialize(output, obj);
        }

        private static T DeserializeWithProtobufNet<T>(Stream input)
        {
            return ProtoBuf.Serializer.Deserialize<T>(input);
        }
        
        private static BinarySerializer binarySerializer = new BinarySerializer();
        private static void SerializeWithNineBinarySerializer<T>(Stream output, T obj)
        {
            binarySerializer.Save(output, obj, null);
        }

        private static T DeserializeWithNineBinarySerializer<T>(Stream input)
        {
            return (T)binarySerializer.Load(input, null);
        }
    }
}
